﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//a 2d char array and associated methods to manipulate it

namespace SCG.General
{
    public class CharMatrix : BaseMatrix
    {
        //constructor
        public CharMatrix(int width, int height)
        {
            _width = width;
            _height = height;
            _buffer = new char[_width, _height];
        }

        //construct with a "pattern string", comma-delimited string
        public CharMatrix(string patternString)
        {
            string[] lines = patternString.Split(',');
            _width = lines[0].Length;
            _height = lines.Length;
            _buffer = new char[_width, _height];
            for(int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                {
                    char c = lines[y][x];
                    _buffer[x, y] = c;
                }
        }

        //construct with a string list
        public CharMatrix(List<string> lines)
        {
            _width = lines[0].Length;
            _height = lines.Count;
            _buffer = new char[_width, _height];
            for (int y = 0; y < lines.Count; y++)
            {
                string s = lines[y];
                for (int x = 0; x < _width; x++)
                    _buffer[x, y] = s[x];
            }
        }

        //get a value
        public char Get(int x, int y)
        {
            return _buffer[x, y];
        }

        //get a value with bounds checking
        public char GetSafely(int x, int y)
        {
            if (InBounds(x, y))
                return _buffer[x, y];
            else
                return '\0';
        }

        //set a value
        public void Set(int x, int y, char value)
        {
            _buffer[x, y] = value;
        }

        //set a value with bounds checking
        public void SetSafely(int x, int y, char value)
        {
            if (InBounds(x, y))
                _buffer[x, y] = value;
        }

        //fill with a value
        public void Fill(char value)
        {
            for (int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                    _buffer[x, y] = value;
        }

        //create a copy
        public CharMatrix Clone()
        {
            CharMatrix cm = new CharMatrix(_width, _height);
            for (int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                    cm._buffer[x, y] = _buffer[x, y];
            return cm;
        }

        //rotate 90 degrees clockwise
        public CharMatrix Twist()
        {
            CharMatrix cm = new CharMatrix(_height, _width);
            for(int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                {
                    int x2 = _height - y - 1;
                    int y2 = x;
                    cm._buffer[x2, y2] = _buffer[x, y];
                }
            return cm;
        }

        //superimpose one over another
        public CharMatrix Superimpose(CharMatrix matrix, char emptyChar)
        {
            CharMatrix cm = Clone();
            for(int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                {
                    char c = matrix._buffer[x, y];
                    if (c != emptyChar)
                        cm._buffer[x, y] = c;
                }
            return cm;
        }

        //paste another matrix on top of this one
        public void Paste(CharMatrix matrix, int x, int y, char emptyChar)
        {
            for(int my = 0; my < matrix._height; my++)
                for (int mx = 0; mx < matrix._width; mx++)
                {
                    char mc = matrix._buffer[mx, my];
                    if (mc != emptyChar)
                    {
                        int dx = x + mx;
                        int dy = y + my;
                        _buffer[dx, dy] = mc;
                    }
                }

        }

        //find first occurance of matching matrix (or null if not found)
        public SCGPoint FindPattern(CharMatrix pattern, int x, int y, bool includeStartPoint)
        {
            if (!includeStartPoint)
            {
                x++;
                if (x >= _width)
                {
                    x = 0;
                    y++;
                }
            }

            while (y < _height - pattern._height)
            {
                while (x < _width - pattern._width)
                {
                    //does this block match?
                    bool match = true;
                    for (int my = 0; my < pattern._height; my++)
                    {
                        for (int mx = 0; mx < pattern._width; mx++)
                        {
                            match = _buffer[x + mx, y + my] == pattern._buffer[mx, my];
                            if (!match)
                                break;
                        }
                        if (!match)
                            break;
                    }
                    if (match)
                        return new SCGPoint(x, y);
                    x++;
                }
                y++;
                x = 0;
            }
            return null;
        }

        //does this matrix match another one at a starting location?
        public bool Matches(CharMatrix region, int x, int y)
        {
            for(int ty = 0; ty < _height; ty++)
                for (int tx = 0; tx < _width; tx++)
                {
                    char c = region._buffer[tx + x, ty + y];
                    if (c != _buffer[tx, ty])
                        return false;
                }
            return true;
        }

        //try to find a pattern within a max range of a point, considering rotations
        public SCGPoint FindPattern(CharMatrix pattern, int x, int y, int maxRange, ref int twists)
        {
            //get all possible search points
            RandomList<SCGPoint> pts = new RandomList<SCGPoint>();
            for(int ty = 0; ty < maxRange; ty++)
                for (int tx = 0; tx < maxRange; tx++)
                {
                    SCGPoint pt = new SCGPoint(x + tx, y + ty);
                    pts.Add(pt);
                }

            //get the 4 orientations of the pattern
            List<CharMatrix> patterns = new List<CharMatrix>();
            patterns.Add(pattern);
            CharMatrix twisted = pattern.Twist();
            patterns.Add(twisted);
            twisted = twisted.Twist();
            patterns.Add(twisted);
            twisted = twisted.Twist();
            patterns.Add(twisted);

            //check each point
            while (pts.Count > 0)
            {
                SCGPoint pt = pts.RemoveRandomValue;
                for (int t = 0; t < 4; t++)
                {
                    CharMatrix checkPattern = patterns[t];
                    if (checkPattern.Matches(this, pt.X, pt.Y))
                    {
                        twists = t;
                        return pt;
                    }
                }
            }
            return null;
        }

        //invert horizontally
        public CharMatrix InvertHorizontally()
        {
            CharMatrix newMatrix = new CharMatrix(_width, _height);
            for(int y = 0; y < _height; y++)
                for (int x = 0; x < _width; x++)
                {
                    char c = _buffer[x, y];
                    int x2 = _width - x - 1;
                    newMatrix._buffer[x2, y] = c;
                }
            return newMatrix;
        }

        //crop
        public CharMatrix Crop(char emptyChar)
        {
            //determine edges
            int leftEdge = _width;
            int rightEdge = 0;
            for (int y = 0; y < _height; y++)
            {
                for(int x = 0; x < _width; x++)
                    if (_buffer[x, y] != emptyChar)
                    {
                        if (x < leftEdge)
                            leftEdge = x;
                        break;
                    }
                for(int x = _width - 1; x >= 0; x--)
                    if (_buffer[x, y] != emptyChar)
                    {
                        if (x > rightEdge)
                            rightEdge = x;
                        break;
                    }
            }

            int topEdge = _height;
            int bottomEdge = 0;
            for (int x = 0; x < _width; x++)
            {
                for(int y = 0; y < _height; y++)
                    if (_buffer[x, y] != emptyChar)
                    {
                        if (y < topEdge)
                            topEdge = y;
                        break;
                    }
                for (int y = _height - 1; y >= 0; y--)
                    if (_buffer[x, y] != emptyChar)
                    {
                        if (y > bottomEdge)
                            bottomEdge = y;
                        break;
                    }
            }

            //fall out?
            if (leftEdge == _width || rightEdge == 0 || topEdge == _height || bottomEdge == 0)
                return null;

            //create cropped copy
            CharMatrix cropped = new CharMatrix(rightEdge - leftEdge + 1, bottomEdge - topEdge + 1);
            for(int y = 0; y < cropped.Height; y++)
                for (int x = 0; x < cropped.Width; x++)
                {
                    char c = _buffer[x + leftEdge, y + topEdge];
                    cropped._buffer[x, y] = c;
                }
            return cropped;
        }

        //private members
        private char[,] _buffer;
    }
}
